

package com.googlecode.flac4j.format.frame.sub;


import com.googlecode.jinahya.io.BitInput;
import com.googlecode.jinahya.io.BitOutput;

import java.io.IOException;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlType;


/**
 *
 * @author Jin Kwon <jinahya at gmail.com>
 */
@XmlType(propOrder = {"samples", "precision", "shift", "coefficients",
                      "residual"})
public class LPC extends AbstractSubData {


    /**
     * Creates a new instance.
     */
    public LPC() {
        super(0x20, 5);
    }


    @Override
    public void read(final BitInput input) throws IOException {

        final int bitsPerSample = getFrameHeader().getSampleSize();
        samples.clear();
        final int order = getOrder();
        for (int i = 0; i < order; i++) {
            samples.add(input.readUnsignedInt(bitsPerSample));
        }

        precision = input.readUnsignedInt(4);

        shift = input.readUnsignedInt(5);

        coefficients = input.readInt(precision * order);

        getResidual().read(input);
    }


    @Override
    public void write(final BitOutput output) throws IOException {

        final int bitsPerSample = getFrameHeader().getSampleSize();
        final int order = getOrder();
        if (samples.size() != order) {
            throw new IllegalStateException(
                "samples.size(" + samples.size() + ") != order(" + order + ")");
        }
        for (int sample : samples) {
            output.writeUnsignedInt(bitsPerSample, sample);
        }

        output.writeUnsignedInt(4, precision);

        output.writeUnsignedInt(5, shift);

        output.writeInt(precision * order, coefficients);

        getResidual().write(output);
    }


    // ----------------------------------------------------------------- samples
    /**
     * Clears samples.
     */
    public void clearSamples() {
        samples.clear();
    }


    /**
     * Returns the number of samples added so far.
     *
     * @return the number of samples
     */
    public int getSampleCount() {
        return samples.size();
    }


    /**
     * Adds given <code>sample</code> to the list.
     *
     * @param sample sample to add
     */
    public void addSample(final int sample) {

        final int bitsPerSample = getFrameHeader().getSampleSize();

        if (sample >> bitsPerSample != 0) {
            throw new IllegalArgumentException(
                "sample(" + sample + ") >> bitsPerSample(" + bitsPerSample
                + ") != 0)");
        }

        samples.add(sample);
    }


    // --------------------------------------------------------------- precision
    /**
     * Returns precision.
     *
     * @return precision
     */
    public int getPrecision() {
        return precision;
    }


    /**
     * Sets precision.
     *
     * @param precision precision
     */
    public void setPrecision(final int precision) {

        if (precision >> 4 != 0) {
            throw new IllegalArgumentException(
                "precision(" + precision + ") >> 4 != 0");
        }

        this.precision = precision;
    }


    // ------------------------------------------------------------------- shift
    /**
     * Returns shift.
     *
     * @return shift
     */
    public int getShift() {
        return shift;
    }


    /**
     * Sets shift.
     *
     * @param shift shift
     */
    public void setShift(final int shift) {

        if (shift >> 5 != 0) {
            throw new IllegalArgumentException(
                "shift(" + shift + ") >> 5 != 0");
        }

        this.shift = shift;
    }

    // ------------------------------------------------------------ coefficients

    /**
     * Returns coefficients.
     *
     * @return coefficients
     */
    public int getCoefficients() {
        return coefficients;
    }


    /**
     * Sets coefficients.
     *
     * @param coefficients coefficients
     */
    public void setCoefficients(final int coefficients) {

        final int order = getOrder();

        if (coefficients >> (precision * order) != 0) {
            throw new IllegalArgumentException(
                "coefficients(" + coefficients
                + ") >> (precision(" + precision + ") * order(" + order
                + ")) != 0");
        }

        this.coefficients = coefficients;
    }


    // ---------------------------------------------------------------- residual
    public Residual getResidual() {

        if (residual == null) {
            residual = new Residual();
        }

        return residual;
    }


    @XmlElement(name = "sample")
    @XmlElementWrapper(required = true)
    private List<Integer> samples = new ArrayList<Integer>();


    @XmlElement(required = true)
    private int precision;


    @XmlElement(required = true)
    private int shift;


    @XmlElement(required = true)
    private int coefficients;


    @XmlElement(required = true)
    private Residual residual;


}

